package org.paic.vertx.httpwrapper

import static groovy.io.FileType.FILES
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration

import org.paic.util.PropHelper

import org.paic.vertx.httpwrapper.Result
import org.paic.vertx.httpwrapper.ChainHandler
import org.paic.vertx.sockjs.ChainHandlerSockjs

import com.alibaba.fastjson.JSON

import org.slf4j.Logger
import org.slf4j.LoggerFactory

import org.vertx.groovy.core.Vertx
import org.vertx.groovy.core.buffer.Buffer
import org.vertx.groovy.core.http.impl.DefaultHttpServerRequest
import org.vertx.groovy.core.http.impl.DefaultHttpServerResponse
import org.vertx.groovy.core.sockjs.SockJSSocket

@CompileStatic
class Server{

	final static String ENCODING = 'utf-8'
	final static String CONFIG_FILE = '/config/server.properties'

	Logger log = LoggerFactory.getLogger(Server.class)

	Vertx vertx

	Properties conf

	long timerIDBinding = 0
	long timerIDSchedule = 0

	public Server(Vertx vertx){
		this.conf = PropHelper.loadProperties(CONFIG_FILE)
		this.vertx = vertx

		MetaClassExtender.getInst().config(this.conf)
	}

	void refreshConfig(){
		this.conf.putAll(PropHelper.loadProperties(CONFIG_FILE))
	}

	void cancelTimer(){
		if(timerIDBinding != 0){
			vertx.cancelTimer(timerIDBinding)
			timerIDBinding = 0
		}
		if(timerIDSchedule != 0){
			vertx.cancelTimer(timerIDSchedule)
			timerIDSchedule = 0
		}
	}

	void listen(int port, String host = 'localhost'){
		ChainHandler handler = new ChainHandler()
		ChainHandlerSockjs handlerSockjs = new ChainHandlerSockjs()

		handler.filter('addSessionVisitTime', handler.PRE, /^.*$/){DefaultHttpServerRequest req, DefaultHttpServerResponse resp ->
			String cookieVal = (String)req.invokeMethod('getCookie', Constants.sessionCookieName)
			if(cookieVal){
				MetaClassExtender.getInst().sessionOpter.setVisit(Constants.sessionStoreKeyPre + cookieVal)
			}

			// continue execute
			false
		}

		handler.filter('defaultHandlerAfterRequest', handler.NEXT, /^.*$/){DefaultHttpServerRequest req, DefaultHttpServerResponse resp ->
			Object obj = req.getLocal(handler.RESULT)
			if(obj == null){
				return false
			}

			if(obj instanceof Result){
				Result r = (Result)obj
				if(r.view != null && r.viewData != null){
					Object[] args = [r.view, r.viewData]
					resp.invokeMethod('render', args)
				}else if(r.json != null){
					resp.invokeMethod('json', r.json)
				}else if(r.output != null){
					resp.end(r.output)
				}else if(r.file != null && r.bytes != null){
					Object[] args = [r.file, r.bytes, r.headers]
					resp.invokeMethod('download', args)
				}

				return r.isNext()
			}

			if(obj instanceof String){
				// direct output
				resp.end((String)obj)
				return true
			}else{
				return false
			}
		}

		// use GroovyShell to execute scripts to extend chain handler
		Binding binding = new Binding()
		binding.setProperty('log', log)
		binding.setProperty('handler', handler)
		binding.setProperty('handlerSockjs', handlerSockjs)
		binding.setProperty('vertx', vertx)
		binding.setProperty('config', conf)

		CompilerConfiguration compileConf = new CompilerConfiguration()
		compileConf.classpath = 'src'
		compileConf.sourceEncoding = ENCODING
		final GroovyShell shell = new GroovyShell(binding, compileConf)

		// dynamical add router/intercepters
		final Map<String, Long> lastModifiedRecords = [:]
		def addBinding = {File file ->
			String absPath = file.absolutePath
			Long modifiedMillis = lastModifiedRecords[absPath]
			// skip if file not modified
			if(modifiedMillis && modifiedMillis == file.lastModified()){
				log.debug('Skip add binding as file not modified: ' + file.name)
				return
			}

			lastModifiedRecords[absPath] = file.lastModified()
			try{
				shell.evaluate(file)
				log.info('Completed to add binding or handler: ' + file.name)
			}catch(ex) {
				log.error('Failed to add binding for handler!', ex)
			}
		}

		def addHandler = {
			File handleDir = new File('src/' + (conf.getProperty('handleDir') ?: 'handler'))
			handleDir.eachFileRecurse(FILES){
				if(it.name.endsWith('.groovy')){
					addBinding(it)
				}
			}
		}

		addHandler()
		if('dev' == conf.getProperty('env')){
			final long defaultInterval = 1000 * 5
			long interval = conf.getProperty('handlerLoadInterval') ? Long.valueOf(conf.getProperty('handlerLoadInterval')) : defaultInterval

			log.info('Watching add binding or handler running!')
			timerIDBinding = vertx.setPeriodic(interval){ timer ->
				refreshConfig()
				addHandler()
			}
		}

		// use cron better
		if('true' == conf.getProperty('schedule')){
			final long defaultInterval = 1000 * 60
			long interval = conf.getProperty('scheduleInterval') ? Long.valueOf(conf.getProperty('scheduleInterval')) : defaultInterval

			timerIDSchedule = vertx.setPeriodic(interval){ timer ->
				for(router in handler.routerList){
					if(!router.urlPat.startsWith('schedule'))
						continue

					router.handler.call(null, null)
				}
			}
		}

		// ****** ****** ****** ****** ****** ****** ****** ******
		// ****** ****** ****** ****** ****** ****** ****** ******
		// run http server
		def server = vertx.createHttpServer()

		server.requestHandler(handler.asClosure())

		// add sockjs support
		String socketioPrefix = conf.getProperty('socketioPrefix')
		if(socketioPrefix == null)
			socketioPrefix = '/socketio'

		vertx.createSockJSServer(server).installApp(prefix: socketioPrefix){SockJSSocket sock ->
			log.info('sockjs client connected : ' + sock.writeHandlerID)

			handlerSockjs.connections << sock

			sock.endHandler{
				handlerSockjs.connections.remove(sock)
				log.info('sockjs client disconnected : ' + sock.writeHandlerID)
			}

			sock.dataHandler{Buffer buff ->
				try {
					String txt = new String(buff.bytes, ENCODING)
					log.info('sockjs client ' + sock.writeHandlerID + ' send : ' + txt)

					def obj = JSON.parseObject(txt)
					obj.id = sock.writeHandlerID
					def result = handlerSockjs.handle(sock, obj)

					if(result)
						sock << JSON.toJSONString(result)
					else
						sock << ''
				}catch(ex){
					sock << JSON.toJSONString([error: ex.message])
				}
			}
		}

		server.toJavaServer().setCompressionSupported('true' == conf.getProperty('compressionSupported'))
		server.listen(port, host)

		log.info('server listened up : ' + host + ':' + port)
	}
}